﻿using System;
using System.Diagnostics;

namespace SfsTools.Core
{
    /// <summary>
    /// Base class for <see cref="Directory"/> and <see cref="File"/>.
    /// </summary>
    internal abstract class FileSystemObject : Object, IFileSystemObject
    {
        #region Properties

        /// <summary>
        /// Contains the file system.
        /// </summary>
        protected readonly FileSystem FFileSystem;
        /// <summary>
        /// Contains the index where this directory is located in the <see cref="IndexArea"/>. Minus one when the directory is not represented in the file system.
        /// </summary>
        protected int FIndexAreaEntryIndex;
        /// <summary>
        /// Indicates whether this object is disposed.
        /// </summary>
        private bool FDisposed;

        #endregion

        #region Methods

        #region Misc

        /// <summary>
        /// Checks that the object is still alive.
        /// </summary>
        [DebuggerStepThrough]
        protected void CheckAlive()
        {
            if (FDisposed)
                throw new ObjectDisposedException(null);
            FFileSystem.CheckAlive();
        }

        /// <summary>
        /// Kills this object.
        /// </summary>
        [DebuggerStepThrough]
        protected void Kill()
        {
            FDisposed = true;
        }

        #endregion

        /// <summary>
        /// Returns the full path to the <see cref="IFileSystemObject"/>.
        /// </summary>
        /// <returns></returns>
        [DebuggerStepThrough]
        public String GetFullPath()
        {
            if ((FParent == FFileSystem.RootDirectory) || (FParent == null))
                return String.Concat(System.IO.Path.DirectorySeparatorChar, FName);
            else
                return String.Concat(FParent.GetFullPath(), System.IO.Path.DirectorySeparatorChar, FName);
        }

        /// <summary>
        /// Gets the <see cref="FileSystemObject"/> entry, if exists.
        /// </summary>
        /// <returns></returns>
        [DebuggerStepThrough]
        public FileSystemObjectEntry GetEntry()
        {
            if (FIndexAreaEntryIndex > 0)
            {
                FileSystemObjectEntry entry = FFileSystem.IndexArea[FIndexAreaEntryIndex] as FileSystemObjectEntry;
                Debug.Assert(entry != null);
                return entry;
            }
            else
            {
                // No entry.
                return null;
            }
        }

        /// <summary>
        /// Invalidates this <see cref="FileSystemObject"/> (removes its index area entry).
        /// </summary>
        [DebuggerStepThrough]
        public void Invalidate()
        {
            FIndexAreaEntryIndex = -1;
        }

        /// <summary>
        /// Renames this <see cref="IFileSystemObject"/> to <paramref name="name"/>.
        /// </summary>
        /// <param name="name">The new name of this <see cref="IFileSystemObject"/>.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="name"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException">The argument <paramref name="name"/> contains invalid characters.</exception>
        /// <exception cref="InvalidOperationException">The parent directory already contains a <see cref="IFileSystemObject"/> with <paramref name="name"/>.</exception>
        /// <exception cref="IndexAreaFullException">Cannot allocate entries because the index area is full.</exception>
        /// <exception cref="System.IO.PathTooLongException">The full path is too long.</exception>
        /// <exception cref="System.IO.IOException">An I/O exception occurred.</exception>
        public abstract void Rename(String name);

        /// <summary>
        /// Deletes this <see cref="IFileSystemObject"/>.
        /// </summary>
        /// <exception cref="InvalidOperationException">The <see cref="IFileSystemObject"/> is already deleted.</exception>
        /// <exception cref="System.IO.IOException">An I/O exception occurred.</exception>
        public abstract void Delete();

        /// <summary>
        /// Restores this <see cref="IFileSystemObject"/> that was previously deleted.
        /// </summary>
        /// <exception cref="InvalidOperationException">The <see cref="IFileSystemObject"/> is not deleted.</exception>
        /// <exception cref="System.IO.IOException">An I/O exception occurred.</exception>
        /// <exception cref="IndexAreaFullException">Cannot create <see cref="IFileSystemObject"/> because the index area is full.</exception>
        public abstract void Resurrect();

        /// <summary>
        /// Accepts a visitor.
        /// </summary>
        /// <param name="visitor">The visitor.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="visitor"/> is <see langword="null"/>.</exception>
        public abstract void AcceptVisitor(IFileSystemObjectVisitor visitor);
        /// <summary>
        /// Accepts a visitor.
        /// </summary>
        /// <param name="visitor">The visitor.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="visitor"/> is <see langword="null"/>.</exception>
        public abstract void AcceptVisitor(IFileSystemObjectInternalVisitor visitor);

        #endregion

        #region Constructors
        /// <overloads>Initializes a new instance of <see cref="FileSystemObject"/>.</overloads>
        /// <summary>
        /// Initializes a new instance of <see cref="FileSystemObject"/>.
        /// </summary>
        /// <param name="fs">The file system.</param>
        [DebuggerStepThrough]
        protected FileSystemObject(FileSystem fs)
            : base()
        {
            FFileSystem = fs;
            FName = String.Empty;
            FIndexAreaEntryIndex = -1;
        }
        /// <summary>
        /// Initializes a new instance of <see cref="FileSystemObject"/>.
        /// </summary>
        /// <param name="fs">The file system.</param>
        /// <param name="parent">The <see cref="Directory"/> this object resides in.</param>
        /// <param name="name">The name of the file system object.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="parent"/> is <see langword="null"/>.</exception>
        [DebuggerStepThrough]
        protected FileSystemObject(FileSystem fs, Directory parent, String name)
            : base()
        {
            if (parent == null)
                throw new ArgumentNullException("parent");
            FFileSystem = fs;
            FParent = parent;
            FName = name;
            FIndexAreaEntryIndex = -1;
        }
        /// <summary>
        /// Initializes a new instance of <see cref="FileSystemObject"/>.
        /// </summary>
        /// <param name="fs">The file system.</param>
        /// <param name="parent">The <see cref="Directory"/> this object resides in.</param>
        /// <param name="name">The name of the file system object.</param>
        /// <param name="index">The zero-based index of the index area entry.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="parent"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The argument <paramref name="index"/> is out of valid range.</exception>
        [DebuggerStepThrough]
        protected FileSystemObject(FileSystem fs, Directory parent, String name, int index)
            : base()
        {
            if (parent == null)
                throw new ArgumentNullException("parent");
            if (index <= 0)
                throw new ArgumentOutOfRangeException("index");
            FFileSystem = fs;
            FParent = parent;
            FName = name;
            FIndexAreaEntryIndex = index;
        }
        /// <summary>
        /// Initializes a new instance of <see cref="FileSystemObject"/>.
        /// </summary>
        /// <param name="fs">The file system.</param>
        /// <param name="parent">The <see cref="Directory"/> this object resides in.</param>
        /// <param name="name">The name of the file system object.</param>
        /// <param name="deleted">Specifies whether this object is deleted.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="parent"/> is <see langword="null"/>.</exception>
        [DebuggerStepThrough]
        protected FileSystemObject(FileSystem fs, Directory parent, String name, bool deleted)
            : base()
        {
            if (parent == null)
                throw new ArgumentNullException("parent");
            FFileSystem = fs;
            FParent = parent;
            FName = name;
            FIndexAreaEntryIndex = -1;
            FIsDeleted = deleted;
        }
        /// <summary>
        /// Initializes a new instance of <see cref="FileSystemObject"/>.
        /// </summary>
        /// <param name="fs">The file system.</param>
        /// <param name="parent">The <see cref="Directory"/> this object resides in.</param>
        /// <param name="name">The name of the file system object.</param>
        /// <param name="index">The zero-based index of the index area entry.</param>
        /// <param name="deleted">Specifies whether this object is deleted.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="parent"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The argument <paramref name="index"/> is out of valid range.</exception>
        [DebuggerStepThrough]
        protected FileSystemObject(FileSystem fs, Directory parent, String name, int index, bool deleted)
            : base()
        {
            if (parent == null)
                throw new ArgumentNullException("parent");
            if (index <= 0)
                throw new ArgumentOutOfRangeException("index");
            FFileSystem = fs;
            FParent = parent;
            FName = name;
            FIndexAreaEntryIndex = index;
            FIsDeleted = deleted;
        }
        #endregion

        #region IFileSystemObject Members

        /// <summary>
        /// Contains the name of the file system object.
        /// </summary>
        protected String FName;
        /// <summary>
        /// Gets the name of the file system object.
        /// </summary>
        public String Name
        {
            [DebuggerStepThrough]
            get
            {
                return FName;
            }
        }
        /// <summary>
        /// Gets the name of this <see cref="IFileSystemObject"/>.
        /// </summary>
        String IFileSystemObject.Name
        {
            [DebuggerStepThrough]
            get
            {
                return FName;
            }
        }

        /// <summary>
        /// Contains a value indicating whether this <see cref="IFileSystemObject"/> is deleted.
        /// </summary>
        protected Boolean FIsDeleted;
        /// <summary>
        /// Gets a value indicating whether this <see cref="IFileSystemObject"/> is deleted.
        /// </summary>
        public Boolean IsDeleted
        {
            [DebuggerStepThrough]
            get
            {
                return FIsDeleted;
            }
        }

        /// <summary>
        /// Gets or sets the time stamp when the <see cref="FileSystemObject"/> was last updated.
        /// </summary>
        public TimeStamp LastUpdate
        {
            [DebuggerStepThrough]
            get
            {
                FileSystemObjectEntry entry = GetEntry();
                if (entry != null)
                    return entry.TimeStamp;
                else
                    return FFileSystem.BootBlock.TimeStamp;
            }
            [DebuggerStepThrough]
            set
            {
                FileSystemObjectEntry entry = GetEntry();
                if (entry != null)
                    entry.TimeStamp = value;
            }
        }
        /// <summary>
        /// Gets the time stamp when the <see cref="IFileSystemObject"/> was last updated.
        /// </summary>
        TimeStamp IFileSystemObject.LastUpdate
        {
            [DebuggerStepThrough]
            get
            {
                FileSystemObjectEntry entry = GetEntry();
                if (entry != null)
                    return entry.TimeStamp;
                else
                    return FFileSystem.BootBlock.TimeStamp;
            }
        }

        /// <summary>
        /// Contains the <see cref="IDirectory"/> this <see cref="IFileSystemObject"/> is located in.
        /// </summary>
        protected readonly Directory FParent;
        /// <summary>
        /// Gets the <see cref="IDirectory"/> this <see cref="IFileSystemObject"/> is located in.
        /// </summary>
        public Directory Parent
        {
            [DebuggerStepThrough]
            get
            {
                return FParent;
            }
        }
        /// <summary>
        /// Gets the <see cref="IDirectory"/> this <see cref="IFileSystemObject"/> is located in.
        /// </summary>
        IDirectory IFileSystemObject.Parent
        {
            [DebuggerStepThrough]
            get
            {
                throw new NotImplementedException();
            }
        }

        /// <summary>
        /// Returns the full path to the <see cref="IFileSystemObject"/>.
        /// </summary>
        /// <returns></returns>
        [DebuggerStepThrough]
        String IFileSystemObject.GetFullPath()
        {
            return GetFullPath();
        }

        /// <summary>
        /// Renames this <see cref="IFileSystemObject"/> to <paramref name="name"/>.
        /// </summary>
        /// <param name="name">The new name of this <see cref="IFileSystemObject"/>.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="name"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException">The argument <paramref name="name"/> contains invalid characters.</exception>
        /// <exception cref="InvalidOperationException">The parent directory already contains a <see cref="IFileSystemObject"/> with <paramref name="name"/>.</exception>
        /// <exception cref="IndexAreaFullException">Cannot allocate entries because the index area is full.</exception>
        /// <exception cref="System.IO.PathTooLongException">The full path is too long.</exception>
        /// <exception cref="System.IO.IOException">An I/O exception occurred.</exception>
        [DebuggerStepThrough]
        void IFileSystemObject.Rename(String name)
        {
            Rename(name);
        }

        /// <summary>
        /// Deletes this <see cref="IFileSystemObject"/>.
        /// </summary>
        /// <exception cref="InvalidOperationException">The <see cref="IFileSystemObject"/> is already deleted.</exception>
        /// <exception cref="System.IO.IOException">An I/O exception occurred.</exception>
        [DebuggerStepThrough]
        void IFileSystemObject.Delete()
        {
            Delete();
        }

        /// <summary>
        /// Accepts a visitor.
        /// </summary>
        /// <param name="visitor">The visitor.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="visitor"/> is <see langword="null"/>.</exception>
        [DebuggerStepThrough]
        void IFileSystemObject.AcceptVisitor(IFileSystemObjectVisitor visitor)
        {
            AcceptVisitor(visitor);
        }

        #endregion
    }
}
